/*
**************************************************************************************
*Myriad Engine                                                                       *
*Copyright (C) 2006-2007, 5d Studios (www.5d-Studios.com)                            *
*                                                                                    *
*This library is free software; you can redistribute it and/or                       *
*modify it under the terms of the GNU Lesser General Public                          *
*License as published by the Free Software Foundation; either                        *
*version 2.1 of the License, or (at your option) any later version.                  *
*                                                                                    *
*This library is distributed in the hope that it will be useful,                     *
*but WITHOUT ANY WARRANTY; without even the implied warranty of                      *
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                   *
*Lesser General Public License for more details.                                     *
*                                                                                    *
*You should have received a copy of the GNU Lesser General Public                    *
*License along with this library; if not, write to the Free Software                 *
*Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA      *
**************************************************************************************
*/
package leola.backend.interpreter;

import leola.types.LeoObject;

/**
 * A separate stack from the {@link Callstack} to hold variables in {@link Scope}.
 * 
 * @author Tony
 *
 */
public class SymbolStack {

	/**
	 * Parent stack - for support
	 * of closures and classes
	 */
	private SymbolStack parent;
	
	/**
	 * Stack
	 */
	private /*java.util.*/Stack<Scope> stack;
	
	/**
	 * The global scope
	 */
	private Scope global;	
	
	/**
	 * @param stack
	 * @param global
	 */
	public SymbolStack(SymbolStack parent) {
		this.parent = parent;
		this.stack = new /*java.util.*/Stack<Scope>();
		this.global = new Scope(true); // we set to true because the global scope may be
									   // pushed on the scope stack by calling global functions
									   // these functions need to be able to traverse up the scope stack.
	}
	
	/**
	 * No parent {@link SymbolStack}
	 */
	public SymbolStack() {
		this(null);
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer(1024);
		for(Scope scope : this.stack) {
			buffer.append(scope.toString()).append("\n");
		}
		
		return "[ Global: " + this.global + " \n" 
			   + " Scopes: " + buffer + " \n]";
	}

	public synchronized int size() {
		return this.stack.size();
	}
	
	/**
	 * Attempts to unwind in the stack without popping.
	 * 
	 * @param depth
	 * @return null if no scope at the supplied depth otherwise the Scope.
	 */
	public synchronized Scope peekBack(int depth) {
		Scope scope = null;
		int size = this.stack.size();
		if ( size >= depth) {
			scope = this.stack.get(size-depth);
		}
		else if ( (depth - size) == 1) {
			scope = this.global;	// tail recursion on the global scope
		}
		
		return scope;
	}
	
	public synchronized void push(Scope scope) {
		this.stack.push(scope);
	}
	
	public synchronized Scope pop() {
		return this.stack.pop();
	}
	
	/**
	 * Peek at the current scope always will return a Scope (global if none
	 * are on the stack.)
	 * @return
	 */
	public Scope peek() {
		Scope scope = this.global;
		if ( ! this.stack.isEmpty() ) { 
			scope = this.stack.peek();
		}
		return scope;
	}
	
	/**
	 * @return the global
	 */
	public Scope getGlobal() {
		return global;
	}
	
	/**
	 * @return the stack
	 */
	public /*java.util.*/Stack<Scope> getStack() {
		return stack;
	}
	
	/**
	 * Will look up starting at the current scope and any parent scopes.  If
	 * the variable isn't found it will finally look in the global scope.
	 * 
	 * @param symbol
	 * @return
	 */
	public LeoObject lookup(String symbol) {
		LeoObject result = null;
		
		int size = this.stack.size();
		for(int i = size - 1; i >= 0; --i) {
			Scope scope = this.stack.get(i);
			result = scope.getSymbolTable().get(symbol);
			if ( result != null || !scope.isEmbedded() ) {
				break;
			}			
		}
		
		/* check the global scope */
		if ( result == null ) {
			result = this.global.getSymbolTable().get(symbol);
			if ( result == null && this.parent != null) {
				result = this.parent.lookup(symbol);
			}
		}
		
		return (result);
	}
	
	/**
	 * Sets a variable
	 * @param symbol
	 * @param value
	 * @return true if set, false otherwise.
	 * 
	 */
	public boolean set(String symbol, LeoObject value) throws Exception {
		LeoObject result = null;
		boolean found = false;
		
		int size = this.stack.size();
		for(int i = size - 1; i >= 0; --i) {
			Scope scope = this.stack.get(i);
			result = scope.getSymbolTable().get(symbol);
			if ( result != null ) {
				scope.getSymbolTable().set(symbol, value);
				found = true;
				break;
			}			
			
			if ( !scope.isEmbedded() ) {
				break;
			}
		}
		
		/* check the global scope */
		if ( result == null ) {
			result = this.global.getSymbolTable().get(symbol);
			if ( result != null ) {
				this.global.getSymbolTable().set(symbol, value);
				found = true;
			}
			else if ( this.parent != null) {
				found = this.parent.set(symbol, value);
			}
		}
		
		return (found);
	}
	
	/**
	 * Clones this object
	 */
	public SymbolStack clone() {
		SymbolStack clone = new SymbolStack();
		clone.global = this.global.clone();
		clone.parent = (this.parent != null) ? this.parent.clone() : null;
		
		for(Scope scope : this.stack) {
			clone.push(scope.clone());
		}
		
		return clone;
	}
	
	/**
	 * Does a shallow copy
	 * @return a shallow copy
	 */
	public synchronized SymbolStack shallowClone() {
		SymbolStack clone = new SymbolStack();
		clone.global = this.global.shallowClone();
		clone.parent = (this.parent != null) ? this.parent.shallowClone() : null;
		
		for(Scope scope : this.stack) {
			clone.push(scope.shallowClone());
		}
		
		return clone;
	}
}
